package com.itheima.service.impl;

import com.itheima.service.RedisIncrLockService;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Service
public class RedisIncrLockServiceImpl implements RedisIncrLockService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void redisLockCD() {
        // 按照我们猜想： 500
        //只要加锁成功， 就一定给这个对应的key设置了过期时间。
        RLock anyLock = redissonClient.getLock("anyLock");
        anyLock.lock(); //
        try {
            Thread.sleep(40000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String num = redisTemplate.opsForValue().get("num");
        Integer intNum = Integer.parseInt(num); //超过了30s
        intNum = intNum + 1;
        redisTemplate.opsForValue().set("num", intNum.toString());

        anyLock.unlock();



    /*    String uuidValue = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuidValue,30, TimeUnit.SECONDS);
        if (lock) {
            // 服务A  服务B
            //现在redis中的锁其实是属于 服务B，但是服务A会接着往下执行，其实服务A删除的锁，就并不是他自己的，而是服务B的锁
            String num = redisTemplate.opsForValue().get("num");
            Integer intNum = Integer.parseInt(num); //超过了30s
            intNum = intNum + 1;
            redisTemplate.opsForValue().set("num", intNum.toString());
            // lua表达式来 去进行拿锁，比锁，删锁的原子性， 只能一个人一个人去执行
            *//*String redisValue = redisTemplate.opsForValue().get("lock");
            //    条件的判断 (是不是我的锁)
             if(uuidValue.equals(redisValue)){
                redisTemplate.delete("lock");
            }*//*
            String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<Long>(lua,Long.class), Arrays.asList("lock"),uuidValue);
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            redisLockCD();
        }*/
    }

    //单体下不加锁
    /*@Override
    public void redisLockTest() {
        //单体下不加锁
        Integer stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            int resultStock = stock - 1;
            redisTemplate.opsForValue().set("stock", resultStock + "");
            System.out.println("库存充足，抢购成功，库存剩余" + resultStock);
        }else{
            System.out.println("库存不足，抢购失败！");
        }
    }*/
    //单体下加锁
   /* @Override
    public void redisLockTest() {
        synchronized (RedisIncrLockServiceImpl.class){
            Integer stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("库存充足，抢购成功，库存剩余" + resultStock);
            }else{
                System.out.println("库存不足，抢购失败！");
            }
        }*/

   //setNX实现分布式锁
    /*@Override
    public void redisLockTest() {
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");
        if (lock) {
            Integer stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("库存充足，抢购成功，库存剩余" + resultStock);
            }else{
                System.out.println("库存不足，抢购失败！");
            }
            redisTemplate.delete("lock");
        }else{
            redisLockTest();
        }
    }*/


    //问题： 如果某个线程在执行过程中宕机，导致没有删除锁？
    /**
     * 死锁解决:
     * • 添加过期时间
     * 但是需要保证抢锁和设置过期时间为原子操作，否则可能会出现抢锁成功后，还没来得及加过期时间就宕机了
     * • 原子性添加过期时间
     */
    /*@Override
    public void redisLockTest() {
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",30,TimeUnit.SECONDS);
        //可能会出现宕机无法释放锁
//        redisTemplate.expire("lock", 30, TimeUnit.SECONDS);
        if (lock) {
            Integer stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("库存充足，抢购成功，库存剩余" + resultStock);
            }else{
                System.out.println("库存不足，抢购失败！");
            }
            redisTemplate.delete("lock");
        }else{
            redisLockTest();
        }
    }*/

    //上述加了过期时间肯导致锁不住的问题（看门狗机制，延期，Redission解决）+删除别人锁（删锁之前一致性校验）的问题：虽然A设置了过期时间，
    // 但是如果A执行业务在过期时间内还没有执行完（网络抖动原因）
    // 过期时间到了导致自动释放锁，这时B就拿到了锁，然后A刚好执行完业务，删除锁，这时，A释放的其实是B的锁
    /**
     * 删除别人锁解决方案
     * 1、当前线程绑定局部变量uuid,删除锁前进行一致性校验
     * 2、使用lua（脚本）表达式解决
     */
    /*@Override
    public void redisLockTest() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
        if (lock) {
            Integer stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("库存充足，抢购成功，库存剩余" + resultStock);
            }else{
                System.out.println("库存不足，抢购失败！");
            }
            String redisLock = redisTemplate.opsForValue().get("lock");
            if (uuid.equals(redisLock)) {
                redisTemplate.delete("lock");
            }
        }else{
            redisLockTest();
        }
    }*/

    /**
     * 引发问题：String redisLock = redisTemplate.opsForValue().get("lock");
     * A拿锁，比锁，删锁不是原子性的，也就是说A拿锁之后，还没比较的时候，A过期了，自动释放锁了，
     * B进来了，B获取锁了，A你删除的锁其实又是B的锁的
     */
/*@Override
public void redisLockTest() {
    String threadUuid = UUID.randomUUID().toString();
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", threadUuid,30,TimeUnit.SECONDS);
    if (lock) {
        Integer stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            int resultStock = stock - 1;
            redisTemplate.opsForValue().set("stock", resultStock + "");
            System.out.println("库存充足，抢购成功，库存剩余" + resultStock);
        }else{
            System.out.println("库存不足，抢购失败！");
        }
        String redisLock = redisTemplate.opsForValue().get("lock");
        *//**
         * 这时候A过期了，自动释放锁了，B进来了，B获取锁了，A你再删除的锁其实又是B的锁的
         *//*
        if (threadUuid.equals(redisLock)) {
            redisTemplate.delete("lock");
        }
    }else{
        redisLockTest();
    }
}*/

    /**
     * 拿锁，比锁，删锁要保证原子性：lua表达式
     */
    @Override
    public void redisLockTest() {
        String threadUuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", threadUuid,30,TimeUnit.SECONDS);
        if (lock) {
            Integer stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("库存充足，抢购成功，库存剩余" + resultStock);
            }else{
                System.out.println("库存不足，抢购失败！");
            }
            /*String redisLock = redisTemplate.opsForValue().get("lock");
            if (threadUuid.equals(redisLock)) {
                redisTemplate.delete("lock");
            }*/
            String lua = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<Long>(lua, Long.class), Arrays.asList("lock"), threadUuid);
        }else{
            redisLockTest();
        }
    }

    @Override
    public void lockTest() {
        synchronized (RedisIncrLockServiceImpl.class) {
            System.out.println("我拿到锁进来了");
            synchronized (RedisIncrLockServiceImpl.class) {
                System.out.println("我又进来了");
            }
        }
    }

    @Override
    public void redissionLockTest() {
        //redission的可重入锁
        RLock anyLock = redissonClient.getLock("anyLock");
        anyLock.lock();
        System.out.println(Thread.currentThread().getId() + "我进来了");
        anyLock.lock();
        System.out.println(Thread.currentThread().getId() + "我又进来了");
        anyLock.unlock();
        anyLock.unlock();
    }


    /**
     * •  如何使用redisson
     * 1. lock.lock();
     * 2. 可重入锁的体现
     * redission 的现象
     * 1. redission是否给我们设置过期时间
     * 2. redission能否续期
     * 3. redission是否会死锁
     */
/*
    @Override
    public void redissionLockTest01() {
        //redission的可重入锁
        *//**
         * 1、帮我们设置过期时间，30s
         * 2、过期时间还有20s的时候自动续期为30s
         * 3、释放锁了之后不再续期
         * 续期不会导致死锁
         *//*
        RLock anyLock = redissonClient.getLock("anyLock");
        anyLock.lock();
        System.out.println(Thread.currentThread().getId() + "我进来了");
        try {
            Thread.sleep(40000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        anyLock.unlock();
    }*/


    /**
     * •  如何使用redisson
     * 1. lock.lock(10, TimeUnit.SECONDS);
     * redission 的现象
     * 1. redission是否给我们设置过期时间
     * 2. redission能否续期
     * 3. redission是否会死锁
     * 4. redission删除别人的锁  不会，抛异常
     */

    @Override
    public void redissionLockTest01() {
        //redission的可重入锁

        RLock anyLock = redissonClient.getLock("anyLock");
        anyLock.lock(5,TimeUnit.SECONDS);
        System.out.println(Thread.currentThread().getId() + "我进来了");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        anyLock.unlock();
    }
}